#Rem
Header: AGFX Extension. 
#End


#if HOST="macos" And TARGET="glfw"
	Import "native/agfx.${TARGET}.mac.${LANG}"
#else
	Import "native/agfx.${TARGET}.${LANG}"
#end

' OpenGL Extension
#if TARGET="html5" or TARGET="android" or TARGET="glfw" or TARGET="ios" or TARGET="flash" Then
	Import "ogl_ext/opengl.${TARGET}.${LANG}"
#end

Import mojo
Import agfx

Extern


#If LANG="cpp" Then

		#Rem
		Summary: return real milliseconds
		#End
		Function RealMillisecs:Int() = "agfx::systemMillisecs"
		
		#Rem
		Summary: - flush keys
		#End
		Function FlushKeys:Void() = "agfx::flushKeys"
		#Rem
		Summary: - hide mouse
		#End
		Function HideMouse:Void() = "agfx::hideMouse"
		#Rem
		Summary: - show mouse
		#End
		Function ShowMouse:Void() = "agfx::showMouse"
		#Rem
		Summary: - get update rate
		#End
		Function GetUpdateRate:Int() = "agfx::getUpdateRate"
		#Rem
		Summary: - set mouse position
			only GLFW, XNA
		#End
		Function SetMouse:Void(x:Int, y:Int) = "agfx::setMouse"
		#Rem
		Summary: - show keyboard
			iOS, Android
		#End
		Function ShowKeyboard:Void() = "agfx::showKeyboard"
		#Rem
		Summary: - launch browser activity
			iOS, Android
		#End
		Function LaunchBrowser:Void(address:String) = "agfx::launchBrowser"
		#Rem
		Summary: - launch email
			iOS, Android
		#End
		Function LaunchEmail:Void(email:String, subject:String, text:String) = "agfx::launchEmail"
		#Rem
		Summary: - set native screen resolution
			GLWF, XNA
		#End
		Function SetNativeGraphicsSize:Void(w:Int, h:Int) = "agfx::setGraphics"
		Function RealMod:Float(value:Float, amount:Float) = "agfx::realMod"
			
		
		' OpenGL extension
		Function SetLineWidth:Void(w:Int) = "opengl::SetLineWidth"
		

		
	#Else
		Function RealMillisecs:Int() = "agfx.systemMillisecs"
		Function FlushKeys:Void() = "agfx.flushKeys"
		Function HideMouse:Void() = "agfx.hideMouse"
		Function ShowMouse:Void() = "agfx.showMouse"
		Function GetUpdateRate:Int() = "agfx.getUpdateRate"
		Function SetMouse:Void(x:Int, y:Int) = "agfx.setMouse"
		Function ShowKeyboard:Void() = "agfx.showKeyboard"
		Function LaunchBrowser:Void(address:String) = "agfx.launchBrowser"			
		Function LaunchEmail:Void(email:String, subject:String, text:String) = "agfx.launchEmail"
		Function SetNativeGraphicsSize:Void(w:Int, h:Int) = "agfx.setGraphics"
		Function RealMod:Float(value:Float, amount:Float) = "agfx.realMod"

		
		' OpenGL extension
		Function SetLineWidth:Void(w:Int) = "opengl.SetLineWidth"
	#End
	
	#If LANG="java" Then
		Function BuildString:String(arr:Int[], offset:Int, length:Int) = "agfx.buildString"
	#End
	
	
'#If LANG="html5" Then
'		Function SetLineWidth:Void(w:Int) = "opengl.SetLineWidth"
'#End

Public

#If LANG <> "java" Then
	Function BuildString:String(arr:Int[], offset:Int, length:Int)
		If offset<0 Or length<=0 Or offset+length > arr.Length Then Return ""
		Local rv:String = String.FromChar(arr[offset])
		For Local i:Int = offset+1 Until offset+length
			rv += String.FromChar(arr[i])
		Next
		Return rv
	End
#End




#Rem
	Summary: - exit from App
#End
Function AppExit:Void()
	Error("")	
End Function

#Rem
	Summary: - return true when two rect are overlaped
#End
Function RectsOverlap:Int(x0:Float, y0:Float, w0:Float, h0:Float, x2:Float, y2:Float, w2:Float, h2:Float)
	If x0 > (x2 + w2) Or (x0 + w0) < x2 Then Return False
	If y0 > (y2 + h2) Or (y0 + h0) < y2 Then Return False
	Return True
End

#Rem
	Summary: - draw line from TVector
#End
Function DrawLineVector(a:TVector2,b:TVector2)
	DrawLine(a.x,a.y,b.x,b.y)
End
#Rem
	Summary: - draw rectangle outline
#End
Function DrawRectOutline:Void(x:Int, y:Int, w:Int, h:Int)
	w -= 1
	h -= 1
	DrawLine(x,y,x+w,y)
	DrawLine(x+w,y,x+w,y+h)
	DrawLine(x+w,y+h,x,y+h)
	DrawLine(x,y+h,x,y)	
End

#Rem
	Summary: - load bitmap
#End
Function LoadBitmap:Image(path$, flags%=0)
	Local pointer:Image = LoadImage(path, 1, flags)

	AssertNotNull(pointer, "Error loading bitmap "+path)
	
   	Return pointer
End

#Rem
	Summary: - load animation bitmap
#End
Function LoadAnimBitmap:Image(path$, w%, h%, count%, tmpImage:Image)
	'tmpImage = loadBitmap(path) <-- This creates another image, decided to just copy the code here
	tmpImage = LoadImage(path)
	
	AssertNotNull(tmpImage, "Error loading bitmap "+path)

	Local pointer:Image = tmpImage.GrabImage( 0, 0, w, h, count, Image.MidHandle)
	
   	Return pointer
End

#Rem
	Summary: - load sound sample
#End
Function LoadSoundSample:Sound(path$)
	Local pointer:Sound = LoadSound(path)
	AssertNotNull(pointer, "Error loading sound "+path)
	Return pointer
End

#Rem
	Summary: - format number
#End
Function FormatNumeric:String(value:Float)
	Local i:Int,s:String,ns:String,k:Int
	Local os:String
	s=String(value)
	os=s
	Local pos:Int=s.Length()
	If s.Find(".")>0 pos=s.Find(".") Else os=""
	For i=pos To 1 Step -1
		If k>2 ns+="." k=0
		k+=1
		ns=ns+Mid(s,i,1)
	Next
	s=""
	For i= ns.Length() To 1 Step -1
		s+=Mid(ns,i,1)
	Next
	If os<>"" s=s+","+os[pos+1..]
	Return s
End

#Rem
	Summary: - return substring from left in size n
#End
Function Left$( str$,n:Int )
	If n>str.Length() n=str.Length()
	Return str[..n]
End

#Rem
	Summary: - return substring from right in size n
#End
Function Right$( str$,n:Int )
	If n>str.Length() n=str.Length()
	Return str[str.Length()-n..]
End

#Rem
	Summary: - return string aligned from left to length n with user defined fill char
#End
Function LSet$( str$,n:Int,char:String=" " )
	Local rep:String
	For Local i:Int=1 To n
		rep=rep+char
	Next
	str=str+rep
	Return str[..n]
End

#Rem
	Summary: - return string aligned from right to length n with user defined fill char
#End
Function RSet$( str$,n:Int,char:String=" " )
	Local rep:String
	For Local i:Int=1 To n
		rep=rep+char
	Next
	str=rep+str
	Return str[str.Length()-n..]
End

#Rem
	Summary: - return substring from pos to size
#End
Function Mid$( str$,pos:Int,size:Int=-1 )
	If pos>str.Length() Return ""
	pos-=1
	If( size<0 ) Return str[pos..]
	If pos<0 size=size+pos pos=0
	If pos+size>str.Length() size=str.Length()-pos
	Return str[pos..pos+size]
End

#Rem
	Summary: - remove backslast from end of path
#End
Function StripDir$( path$ )
	Local i:=path.FindLast( "/" )
	If i<>-1 Return path[i+1..]
	Return path
End
#Rem
	Summary: - remove extension from path
#End
Function StripExt$( path$ )
	Local i:=path.FindLast( "." )
	If i<>-1 And path.Find( "/",i+1 )=-1 Return path[..i]
	Return path
End

#Rem
	Summary: - strip / and extension
#End
Function StripAll$( path$ )
	Return StripDir( StripExt( path ) )
End

#Rem
	Summary: - round float number
#End
Function Round%(flot#)
	Return Floor(flot+0.5)
End

#Rem
	Summary: - return true when point is inside
#End
Function PointInSpot:Int(x1:Float, y1:Float, x2:Float, y2:Float, radius:Float)
	Local dx:Float = x2 - x1
	Local dy:Float = y2 - y1
	Return dx * dx + dy * dy <= radius * radius
End

#Rem
	Summary: - any kay pressed check
#End
Function AnyInputPressed:Bool()
	For Local i:Int = 0 To 511
		If KeyHit(i) Then Return True
	Next
	Return False
End

#Rem
	Summary: - wrap angle to 0- 360 range
#End
Function WrapAngle360:Float(angle:Float)
	
	'Local w:Float = a/360.0
	'Return (w - Floor(w))*360.0
	Return RealMod(angle  , 360.0)
	
End Function

Function WrapAngle180(angle:Float)
	if angle<0 Then
		Return RealMod(angle  - 180.0, 360.0) + 180.0
	EndIf
	if angle>0 Then
		Return RealMod(angle  + 180.0, 360.0) - 180.0
	EndIf
End

#Rem
	Summary: - format number
#End
Function FormatNumber:String(number:Float, decimal:Int=4, comma:Int=0, padleft:Int=0 )
	Assert(decimal > -1 And comma > -1 And padleft > -1, "Negative numbers not allowed in FormatNumber()")

	Local str:String = number
	Local dl:Int = str.Find(".")
	If decimal = 0 Then decimal = -1
	str = str[..dl+decimal+1]
	
	If comma
		While dl>comma
			str = str[..dl-comma] + "," + str[dl-comma..]
			dl -= comma
		Wend
	End
	
	If padleft
		Local paddedLength:Int = padleft+decimal+1
		If paddedLength < str.Length Then str = "Error"
		str = RSet(str,paddedLength)
	End
	Return str
End


#Rem
	Summary: - is char whitespace
#End
Function IsWhitespace:Bool(str:String)
	Return str = "~t" Or str = "~n" Or str = "~r" Or str = " "
End

#Rem
	Summary: - is char whitespace
#End
Function IsWhitespace:Bool(val:Int)
	Return val = ASC_TAB Or val = ASC_LF Or val = ASC_CR Or val = ASC_SPACE
End

' TODO: allow encoding of Int[]
' TODO: enabling lineWrap will wrap the string every lineWrapWidth characters (defaults to 80)
#Rem
	Summary: EncodeBase64
#End

Function EncodeBase64:String(src:String, padOutput:Bool=False, lineWrap:Bool=False, lineWrapWidth:Int=80)
	Local rv:String = ""
	Local s1:Int, s2:Int, s3:Int, a:Int, b:Int, c:Int, d:Int, i:Int
	If src.Length = 0 Then Return ""
	Repeat
		s1 = src[i]
		If i+1 < src.Length Then s2 = src[i+1] Else s2 = 0
		If i+2 < src.Length Then s3 = src[i+2] Else s3 = 0
		a = s1 Shr 2
		b = ((s1 & 3) Shl 4) | (s2 Shr 4)
		c = ((s2 & 15) Shl 2) | (s3 Shr 6)
		d = s3 & 63
		If i+1 >= src.Length Then c = 64
		If i+2 >= src.Length Then d = 64
		rv += BASE64_CHARS[a..a+1] + BASE64_CHARS[b..b+1]
		If c < 64 Or padOutput Then rv += BASE64_CHARS[c..c+1]
		If d < 64 Or padOutput Then rv += BASE64_CHARS[d..d+1]
		i += 3
	Until i >= src.Length
	Return rv
End

' TODO: convert this to use BASE64_ARRAY
' I'll do this once Monkey has a decent string builder class.
#Rem
	Summary: DecodeBase64
#End
Function DecodeBase64:String(src:String)
	Local rv:String = "", src2:String = ""
	Local s1:Int, s2:Int, s3:Int, a:Int, b:Int, c:Int, d:Int, i:Int
	' remove any non-base64 characters
	For i = 0 Until src.Length
		If BASE64_CHARS.Find(src[i..i+1]) >= 0 Then src2 += src[i..i+1]
	Next
	If src2.Length = 0 Then Return ""
	i = 0
	Repeat
		a = BASE64_CHARS.Find(src2[i..i+1])
		If i+1 > src2.Length Then Exit ' This shouldn't happen with base64, so something's wrong!
		b = BASE64_CHARS.Find(src2[i+1..i+2])
		If i+2 < src2.Length Then c = BASE64_CHARS.Find(src2[i+2..i+3]) Else c = 64
		If i+3 < src2.Length Then d = BASE64_CHARS.Find(src2[i+3..i+4]) Else d = 64
		s1 = (a Shl 2) | (b Shr 4)
		s2 = ((b & 15) Shl 4) | (c Shr 2)
		s3 = ((c & 3) Shl 6) | d
		rv += String.FromChar(s1)
		If c <> 64 Then rv += String.FromChar(s2)
		If d <> 64 Then rv += String.FromChar(s3)
		i += 4
	Until i >= src2.Length
	Return rv
End

#Rem
	Summary: - DecodeBase64Bytes
#End
Function DecodeBase64Bytes:Int[](src:String)
	InitBase64()
	Local a:Int, b:Int, c:Int, d:Int, i:Int, j:Int
	Local src2:Int[] = New Int[src.Length]
	Local padding:Int = 0
	
	' find out how many base64 characters
	Local srclen:Int = 0
	For i = 0 Until src.Length
		If BASE64_ARRAY[src[i]] >= 0 Then
			src2[srclen] = src[i]
			srclen += 1
			' check if it's a padding character and increment the count
			If BASE64_ARRAY[src[i]] = 64 Then padding += 1
		End
	Next
	
	' die if there are no base64 chars
	If srclen = 0 Then Return []
	
	' get the target length and create the array
	Local len:Int = 3*(srclen/4)
	If srclen Mod 4 = 0 Then
		len -= padding
	ElseIf padding = 0 Then
		If srclen Mod 4 >= 2 Then len += 1
		If srclen Mod 4 = 3 Then len += 1
	End
	Local rv:Int[] = New Int[len]
	
	i = 0
	j = 0
	Repeat
		a = BASE64_ARRAY[src2[i]]
		If i+1 > srclen Then Exit ' This shouldn't happen with base64, so something's wrong!
		b = BASE64_ARRAY[src2[i+1]]
		If i+2 < srclen Then c = BASE64_ARRAY[src2[i+2]] Else c = 64
		If i+3 < srclen Then d = BASE64_ARRAY[src2[i+3]] Else d = 64
		rv[j] = (a Shl 2) | (b Shr 4)
		If j+1 < len Then rv[j+1] = ((b & 15) Shl 4) | (c Shr 2)
		If j+2 < len Then rv[j+2] = ((c & 3) Shl 6) | d
		i += 4
		j += 3
	Until i >= srclen
	Return rv
End
#Rem
	Summary: InitBase64
#End
Function InitBase64:Void()
	If BASE64_ARRAY.Length = 0 Then
		BASE64_ARRAY = New Int[256]
		Local i% = 0
		For i = 0 Until BASE64_ARRAY.Length
			BASE64_ARRAY[i] = -1
		Next
		For i = 0 Until BASE64_CHARS.Length
			BASE64_ARRAY[BASE64_CHARS[i]] = i
		Next
	End
End






' ***********************************************************************************
' ARRAYS
' ***********************************************************************************
#Rem
	Summary: - helper for create float 2D array
#End
Function FloatArray2D:Float[][] (X:Int,Y:Int)
	Local tmp:Float[X][]
	For Local I% = 0 Until X
		tmp[I] = New Float[Y]
	Next
	Return tmp
End Function
#Rem
	Summary: helper for create int 2D array
#End
Function IntArray2D:Int[][] (X:Int,Y:Int)
	Local tmp:Int[X][]
	For Local I% = 0 Until X
		tmp[I] = New Int[Y]
	Next
	Return tmp
End Function


#Rem
	Summary: - return time formated string from milliseconds
#End
Function MilisecondsToTime:String(ms:Int)
	Local res:String = ""
	
	Local timeMillis:Int = ms
	Local time:Int = timeMillis / 1000  
	Local seconds:String = (time Mod 60)
	Local minutes:String = ((time Mod 3600) / 60)
	Local hours:String = (time / 3600)

	If (seconds.Length()=1) Then seconds = "0"+seconds
	If (minutes.Length()=1) Then minutes = "0"+minutes
	If (hours.Length()=1) Then hours = "0"+hours
	
	Return hours+":"+minutes+":"+seconds
	
End

#Rem
	Summary: return FlashArray created From file (used as vertices list for Box2D)
#End
Function CreateFlashArrayFromFile:FlashArray<b2Vec2>(filename:String)
	Local res:FlashArray<b2Vec2>= New FlashArray<b2Vec2>()
	Local file:String = LoadString("shapes/"+filename)
	
	If file.Length()=0 Then Error("Shape file: .data/shapes/"+filename+" doesn't exist!")
	Local verts:String[] = file.Split("|")
	For Local i:Int=0 to verts.Length()-1
		Local coord:String[]=verts[i].Split(",")
		Local x:Float = Float(coord[0])
		Local y:Float = Float(coord[1])
		res.Push(New b2Vec2(x,y))
	Next
	Return res
End

#Rem
	Summary: number convertor
	Convert a number between arbitrary bases
	Returns a String containing number represented in base tobase. The base in which
	number is given is specified in frombase. Both frombase And tobase have To be
	between 2 And 36, inclusive. Digits in numbers with a base higher than 10 will
	be represented with the letters a-z, with a meaning 10, b meaning 11 And z meaning 35.
#End
Function base_convert:String( number:String, frombase:Int, tobase:Int )
	If ( frombase > 1 And frombase < 37 And tobase > 1 And tobase < 37 )
		Local charlist$ = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
		Local inputArray:Int[] = New Int[number.Length]
		Local outputStack:StringStack = New StringStack
		Local givenLength% = 0
		Local output$ = ""
		Local outVal% = 0
		Local calcVal% = 0
		Local calcMod% = 0
		Local baseMul% = 1
		Local cnt% = 0
		
		number = number.ToUpper()
		
		' So as a quick explanation of what's going on
		' Step one, push the individual values in their decimal equivalent form to the top of the stack
		givenLength = number.Length - 1
		For cnt=0 To givenLength
			inputArray[cnt] = charlist.Find( number[cnt..cnt+1] )
		Next
		
		' Step two, starting from the rightmost value at 1, start to multiply the value that the
		' decimal value is then multiplied by to get the decimal equivalent of whatever base
		' the inputted number is.
		For cnt=inputArray.Length() - 1 To 0 Step - 1
			If ( cnt < inputArray.Length() - 1 )
				baseMul *= frombase
			End
			calcVal += inputArray[cnt] * baseMul
		Next
		
		
		' Step three, the math formula that finds the values in decimal form
		While ( calcVal <> 0 )
			calcMod = calcVal Mod tobase
			outputStack.Push( calcMod )
			calcVal -= calcMod
			calcVal /= tobase
		Wend
		
		' Step four, pop the value from the stack to get the slice location in the charlist string
		givenLength = outputStack.Length()
		For cnt = 1 To givenLength
			outVal = Int(String(outputStack.Pop()))
			output += charlist[outVal..outVal+1]
		Next
		If output="" Then output="0"
		Return output
	End
	Return "0"
End




